-module(instance_manager).

-behaviour(gen_server).

%% API
-export([start_link/0]).
-export([deploy/2,undeploy/1,undeploy/2,publish/1,stop_instances/1]).

-export([lookup_containers_by_app/1,lookup_apps_by_container/1,lookup/2]).

-export([delete/2]).
%%-export([lookup/2,lookup_apps_by_container/1,lookup_containers_by_app/1]).
-export([query_instance_term/0]).
-export([query_instance/1]).
-export([query_app/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).
-record(state, {}).


-include("xengine.hrl").
-include_lib("stdlib/include/qlc.hrl").




delete(ContainerName , AppName ) ->
        configuration_manager:delete( #instance{container_name=ContainerName , 
                                                          app_name=AppName}).

insert(ContainerName,AppName)->
        configuration_manager:insert(#instance {container_name=ContainerName, 
                                                        app_name=AppName}).


lookup(instance , { ContainerName,AppName } )->
	configuration_manager:do(qlc:q([X || X <- mnesia:table(instance),       X#instance.container_name=:=ContainerName,X#instance.app_name=:=AppName])).

lookup_containers_by_app(AppName)->
        configuration_manager:do(qlc:q([Y || X <- mnesia:table(instance), Y <- mnesia:table(container) ,      X#instance.container_name=:=Y#container.name,  X#instance.app_name=:=AppName])).
        
lookup_apps_by_container(ContainerName)->
         configuration_manager:do(qlc:q([Y || X <- mnesia:table(instance), Y <- mnesia:table(app) ,      X#instance.app_name=:=Y#app.name,  X#instance.container_name=:=ContainerName])).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

query_instance_term()->
        gen_server:call(?MODULE, { query_instance_term } , 12000).

query_instance(ContainerName)->
        gen_server:call(?MODULE, { query_instance , ContainerName} , 12000).

query_app(AppName)->
        gen_server:call(?MODULE, { query_app , AppName} , 12000).



undeploy(_App)->
	ok.
undeploy(container,ContainerName)->
        gen_server:call(?MODULE,{undeploy , ContainerName} );
undeploy(AppName,ContainerName)->
        gen_server:call(?MODULE,{undeploy , {AppName,ContainerName}} ).

%%deploy(AppName,ContainerName) when  is_list(AppName)->
%%        case app_manager:lookup(AppName) of
%%                [] ->  
%%                       { error  , "no such a app named:"++AppName };
%%                [App] ->
%%                        gen_server:call(?MODULE,{ deploy , {App , ContainerName }  })
%%        end;

deploy(AppName,ContainerName)->
	gen_server:call(?MODULE,{ deploy , {AppName , ContainerName }  }).

publish(AppName) ->
	gen_server:call(?MODULE, { publish, AppName} , 5000).

stop_instances(App) ->
	gen_server:call(?MODULE, { stop_instances, App } , 100000).


%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
	Instances = configuration_manager:do(qlc:q([X || X <- mnesia:table(instance)])),
	Runtime=#runtime{},
	lists:foreach( fun(Instance) ->
				configuration_manager:insert(
					#instance_runtime{ instance = Instance,
						runtime = Runtime})
			end, Instances),
	{ok, #state{}}.	
%%	case timer:apply_interval(15000,xengine_algo,connect,[]) of
%%		{ok, _ } ->
%%			error_logger:info_msg("algo timer successfully...");
%%		{error,_} ->
%%			error_logger:info_msg("algo timer fail .....")
%%	end,
%%	case timer:apply_interval(8000,xengine_prediction,connect,[]) of
%%		{ok, _ } ->
%%			error_logger:info_msg("time interval successful"),
%%			{ok, #state{}};
%%		{error, _} ->
%%			error_logger:info_msg("time interval fai....")
%%	end.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({query_instance,ContainerName}=Msg,_From , State)->
        error_logger:info_msg("instance_manager:recving a msg=~p~n", [Msg]),   
        case configuration_manager:do(qlc:q([X || X <- mnesia:table(container_runtime),
                                                    X#container_runtime.name=:=ContainerName])) of
           [] ->
                { reply , { error , "no such a container named:"++ ContainerName}, State};                                                    
           [ContainerRuntime] ->
                case  configuration_manager:do(qlc:q([{Z,Y#instance_runtime.runtime} ||
                                                     X <- mnesia:table(instance),
        	                                     Y <- mnesia:table(instance_runtime),
        	                                     Z <- mnesia:table(app),
        	                                     X=:=Y#instance_runtime.instance,
        	                                     X#instance.container_name=:=ContainerName,
                                                     Z#app.name=:=X#instance.app_name  ])) of
                      [] ->
                           {reply , {ContainerRuntime , []} , State };
                      L ->                   
                           error_logger:info_msg("L=~p~n", [L]),         
                           T =   ets:new(?MODULE,[ordered_set,private]),
                           AccResult=container:query_instance_acc(ContainerName) ,
                         error_logger:info_msg("AccResult=~p~n", [AccResult]),         
                           ets:insert(T, AccResult) ,   
                           error_logger:info_msg("query_instance:T=~p~n",[ ets:tab2list(T)]), 
                           F=fun({App,Runtime},X)->
                                  error_logger:info_msg("query_instance:App=~p~n",[ App]), 
                                  case   ets:lookup(T, App#app.name) of
                                        [] ->
                                                 {{App,Runtime ,#total_runtime{}},X};
                                        [{_,Any}] ->
                                                 error_logger:info_msg("query_instance:Any=~p~n",[ Any]), 
                                                  {{App , Runtime , Any },X}
                                   end
                           end,       
                           { R ,_ } = lists:mapfoldl(F,T,L),      
                           error_logger:info_msg("R=~p~n",[ R]),                                              
                           {reply , { ContainerRuntime , R }, State }
                                                   
                                                   
                                
                end                                                     
        end;                                                    
       

handle_call({query_instance_term} , _From, State )->
         case configuration_manager:do(qlc:q([  X || X <- mnesia:table(container) ])) of
           [] ->
                { reply , { error , "No any containers" } , State };
           Containers ->
%%                Nodes=[ list_to_atom("agent@"++X#container.host) ||  X<-Containers ],
                 Nodes=[ get_agent_node(X#container.host) ||  X<-Containers ],
                { Replys , BadNodes }=gen_server:multi_call(Nodes,agent_daemon, 
                               {query_instance_term},20000 ),
%%                error_logger:info_msg("~p~n",[{Replys , BadNodes}]),                               
                case  BadNodes of
                        [] ->
                                { reply , 
                                 lists:zipwith(  fun(Container , {_Node , L })->
                                                        { Container , L }
                                                 end,  Containers ,   Replys) ,
                                   State };
                        _ -> 
                                T =   ets:new(?MODULE,[ordered_set,private]),
                                ets:insert(T , Replys ) , 
                                F=fun(Container)->
                                        Node = get_agent_node(Container#container.host),
                                        case   ets:lookup( T , Node) of
                                               [] ->
                                                    { Container , {error , "query agent error"}};
                                               [{ Node ,L}] ->
%%                                                     error_logger:info_msg("L is ~p~n",[L]),
                                                     {Container , L};
                                               Any ->      
                                                     error_logger:info_msg("instance_manager:Any is ~p~n",[Any]),
                                                     { Container , Any }  
                                        end
                                 end,
                                { reply , 
                                  lists: map (F , Containers ),
                                  State }                                
                end
         end;
handle_call({query_app,AppName},_From , State)->
         case configuration_manager:do(qlc:q([X || X <- mnesia:table(app),
                                                    X#app.name=:=AppName])) of
           [] ->
                   { reply , { error , "no such a app named:"++ AppName}, State};         
           [App] ->
                     {reply , 
                        { App, 
                                 configuration_manager:do(qlc:q([{W,X,Y} ||
                                         W <- mnesia:table(container),
                                         X <- mnesia:table(instance_runtime),
        	                         Y <- mnesia:table(container_runtime),
        	                         Z <- mnesia:table(instance),
        	                         W#container.name=:=Z#instance.container_name,
        	                         X#instance_runtime.instance=:=Z,
        	                         Z#instance.container_name=:=Y#container_runtime.name,
        	                         Z#instance.app_name=:=AppName  ])) },
        	          State}
            end;



        


handle_call({undeploy,{AppName,ContainerName}},_From , State)->
	{reply, do_undeploy(AppName,ContainerName) , State };
handle_call({undeploy , ContainerName}=Msg , _From ,State ) ->
        error_logger:info_msg("instance_manager:recving a msg=~p~n",[Msg]),
        {reply , do_undeploy(container,ContainerName), State };
        
handle_call({deploy,{AppName,ContainerName}},_From , State)->
       case    app_manager:lookup(AppName) of 
                [] ->
                        { reply , { error , "no such a app named:"++ AppName } , State };
                [App] ->
                	{reply, do_deploy(App, ContainerName  , mandatory ) , State} 
        end;
handle_call({publish, AppName}, _From, State) ->
        case    app_manager:lookup(AppName) of 
                [] ->
                        { reply , { error , "no such a app named:"++ AppName } , State };
                [App] ->
                	{reply, do_publish(App), State} 
        end;
handle_call({stop_instances, App}, _From, State) ->
	{reply, do_stop_instances(App), State};
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast( {deploy_report, {_App,_Container}, ok }  , State) ->
        {noreply, State };
handle_cast( {deploy_report, {App,Container}, {error , _Reason } }  , State) ->
        Instance=#instance{container_name=Container#container.name,
                                app_name=App#app.name},                                                           
        configuration_manager:delete(Instance),
        configuration_manager:delete(instance_runtime, Instance),                               
        {noreply, State };
handle_cast(Msg={undeploy ,{AppName,ContainerName}}, State) ->
        error_logger:info_msg("instance_manager recving a undeploy msg : ~p, ~n" , [Msg]),
        do_undeploy(AppName,ContainerName),
        {noreply, State };
handle_cast({deploy,{App,ContainerName} } , State)->
        do_deploy(App, ContainerName  , mandatory ) ,
        {noreply,  State} ;
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
        
 
    {ok, State}.

do_publish(App) ->
	error_logger:info_msg("publish the app :~p ~n" ,[App]),
        F = fun(ContainerName) ->
		do_deploy(App, ContainerName  , mandatory ) 
	end,
	case lookup_containers_by_app(App#app.name) of 
		[] ->
			 error_logger:info_msg("the app hasn't any  instances~n "),
			 case    deploy_policy:select_container(App,2) of
			         [] -> 
	                                {error , "no container can't be selected " } ;
	                         ContainerNames ->
	                                error_logger:info_msg("selecting containers:~p~n",[ContainerNames]),
                                	lists:foreach(F, ContainerNames),
                                	ok
			 end;
		Containers ->
                        lists:foreach(F, [X#container.name || X <- Containers]),
                        ok
	end.


do_stop_instances(App) ->
	F = fun(Container) ->
		do_undeploy(App, Container#container.name) 
	end,

	case lookup_containers_by_app(App) of 
		[] ->
			ok;
		Any ->
			lists:foreach(F, Any),
			ok
	end.


%%--------------------------------------------------------------------
%%%  将一个app部署到某个container上
%%--------------------------------------------------------------------
%%do_deploy(App,Container) when is_record(Container,container)->
%%        do_deploy(App,Container#container.name ,  non_mandatory).
        
do_deploy(App,ContainerName , non_mandatory) ->
        error_logger:info_msg("non_mandatory : deploy the app:~p to the container :~p ",[App,ContainerName]),
	case  lookup(instance, {ContainerName , App#app.name} )  of
		[_] ->
			{error, "The instance is exist " }	;
		[]  ->
%%			[ContainerRuntime]=configuration_manager:lookup(container_runtime ,ContainerName),
			case container:deploy(ContainerName,App) of
			        ok ->
                                     insert(ContainerName,App#app.name ),
                                     configuration_manager:insert(
                                        #instance_runtime{ instance=#instance{container_name=ContainerName,
                                                                                app_name=App#app.name},
                                                           runtime=#runtime{}  });
			        {error , Reason } ->
			             {error ,Reason }
			end
	end;
do_deploy(App,ContainerName ,  	mandatory) ->
        error_logger:info_msg("mandatory : deploy the app:~p to the container :~p ~n",[App,ContainerName]), 
%%        [ContainerRuntime]=configuration_manager:lookup(container_runtime ,ContainerName),
        case container:deploy(ContainerName,App) of
			        ok ->
			           %%  error_logger:info_msg("mandatory : deploy the app:~p to the container :~p  success~n",[App,ContainerName]),    
                                     insert(ContainerName,App#app.name ),
                                      configuration_manager:insert(
                                        #instance_runtime{ instance=#instance{container_name=ContainerName,
                                                                                app_name=App#app.name},
                                                           runtime=#runtime{}  });
			        {error , Reason } ->
			                error_logger:info_msg("mandatory : deploy the app:~p to the container :~p  error , Reason=~p~n",[App,ContainerName,Reason]), 
			             {error ,Reason }
	end.
        
%%强制undeploy某个container的所有应用        
do_undeploy(container,ContainerName)->
 	case container:undeploy(ContainerName,all) of
 	        ok ->
                        case configuration_manager:do(qlc:q([X || X <- mnesia:table(instance), 
                                                                X#instance.container_name=:=ContainerName])) of
                                [] ->
                                        ok ;
                                Instances ->
                                         error_logger:info_msg("instance_manager:do_undeploy , Instances:~p~n",[Instances]),
                                        lists:foreach(  fun (Instance) ->
                                                       F = fun()->
                                                          error_logger:info_msg("delete instance/instance_runtime instance:~p~n",[Instance]),
                                                          configuration_manager:delete(Instance),
                                                          configuration_manager:delete(instance_runtime , Instance)
                                                       end ,
                                                       mnesia:transaction(F)
                                                end , Instances ),
                                         case   configuration_manager:delete(container,ContainerName) of
                                              ok ->
                                                    case configuration_manager:delete(container_runtime,ContainerName) of
                                                        ok ->  
                                                                ok;
                                                        {error,Reason} ->
                                                                { error ,Reason }
                                                    end;
	                                      {error ,Reason } ->
	                                          { error , Reason} 
	                                          
                                         end
                                    
                                                                 
                        end;
 	                
 	        { error , Reason } ->
 	                { error , Reason }
 	
 	end;
 	
do_undeploy(AppName,ContainerName) -> 
        case  lookup(instance, {ContainerName , AppName} )  of
		[] ->
			{error, "No such  instance " }	;
		[_]  ->
%%			[ContainerRuntime]=configuration_manager:lookup(container_runtime , ContainerName),
                        case container:undeploy(ContainerName,AppName) of
                                ok ->
%%                                       error_logger:info_msg("delete instance : app=~p container=~p~n" , [AppName , ContainerName ]),
                                        Instance = #instance{container_name=ContainerName,app_name=AppName} ,
                  		        configuration_manager:delete(Instance), 
                  		        configuration_manager:delete(instance_runtime ,Instance),
                  		        error_logger:info_msg("undeploy  ~p at ~p success ~n" , [AppName , ContainerName ]),
                  		        ok;
                  		{error , Reason } ->
                  		        error_logger:info_msg("undeploy  ~p at ~p error , reason : ~n" , [AppName , ContainerName  , Reason]),
                  		        { error , Reason } ;
                  		Any  ->
                  		        Any
                        end


	end.

get_agent_node(Host) ->
        list_to_atom( "agent@" ++ Host ).


